CodeMirror.defineMode('shell', function (config) {

    var atoms = ['true', 'false'],
        keywords = ['if', 'then', 'do', 'else', 'elif', 'while', 'until', 'for', 'in', 'esac', 'fi', 'fin', 'fil', 'done', 'exit', 'set', 'unset', 'export', 'function'],
        commands = ['ab', 'awk', 'bash', 'beep', 'cat', 'cc', 'cd', 'chown', 'chmod', 'chroot', 'clear', 'cp', 'curl', 'cut', 'diff', 'echo', 'find', 'gawk', 'gcc', 'get', 'git', 'grep', 'kill', 'killall', 'ls', 'make', 'mkdir', 'openssl', 'mv', 'nc', 'node', 'npm', 'ping', 'ps', 'restart', 'rm', 'rmdir', 'sed', 'service', 'sh', 'shopt', 'shred', 'source', 'sort', 'sleep', 'ssh', 'start', 'stop', 'su', 'sudo', 'tee', 'telnet', 'top', 'touch', 'vi', 'vim', 'wall', 'wc', 'wget', 'who', 'write', 'yes', 'zsh'];

    function tokenBase(stream, state) {

        var sol = stream.sol();
        var ch = stream.next();

        if (ch === '\'' || ch === '"' || ch === '`') {
            state.tokens.unshift(tokenString(ch));
            return tokenize(stream, state);
        }
        if (ch === '#') {
            if (sol && stream.eat('!')) {
                stream.skipToEnd();
                return 'meta'; // 'comment'?
            }
            stream.skipToEnd();
            return 'comment';
        }
        if (ch === '$') {
            state.tokens.unshift(tokenDollar);
            return tokenize(stream, state);
        }
        if (ch === '+' || ch === '=') {
            return 'operator';
        }
        if (ch === '-') {
            stream.eat('-');
            stream.eatWhile(/\w/);
            return 'attribute';
        }
        if (/\d/.test(ch)) {
            stream.eatWhile(/\d/);
            if (!/\w/.test(stream.peek())) {
                return 'number';
            }
        }
        stream.eatWhile(/\w/);
        var cur = stream.current();
        if (stream.peek() === '=' && /\w+/.test(cur)) return 'def';
        if (atoms.indexOf(cur) !== -1) return 'atom';
        if (commands.indexOf(cur) !== -1) return 'builtin';
        if (keywords.indexOf(cur) !== -1) return 'keyword';
        return 'word';
    }

    function tokenString(quote) {
        return function (stream, state) {
            var next, end = false, escaped = false;
            while ((next = stream.next()) != null) {
                if (next === quote && !escaped) {
                    end = true;
                    break;
                }
                if (next === '$' && !escaped && quote !== '\'') {
                    escaped = true;
                    stream.backUp(1);
                    state.tokens.unshift(tokenDollar);
                    break;
                }
                escaped = !escaped && next === '\\';
            }
            if (end || !escaped) {
                state.tokens.shift();
            }
            return (quote === '`' || quote === ')' ? 'quote' : 'string');
        };
    };

    var tokenDollar = function (stream, state) {
        if (state.tokens.length > 1) stream.eat('$');
        var ch = stream.next(), hungry = /\w/;
        if (ch === '{') hungry = /[^}]/;
        if (ch === '(') {
            state.tokens[0] = tokenString(')');
            return tokenize(stream, state);
        }
        if (!/\d/.test(ch)) {
            stream.eatWhile(hungry);
            stream.eat('}');
        }
        state.tokens.shift();
        return 'def';
    };

    function tokenize(stream, state) {
        return (state.tokens[0] || tokenBase)(stream, state);
    };

    return {
        startState: function () {
            return {tokens: []}
        },
        token: function (stream, state) {
            if (stream.eatSpace()) return null;
            return tokenize(stream, state);
        }
    };
});

CodeMirror.defineMIME('text/x-sh', 'shell');
